package com.heima.wemedia.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.stream.StreamUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.heima.apis.article.IArticleClient;
import com.heima.common.dtos.ResponseResult;
import com.heima.common.enums.AppHttpCodeEnum;
import com.heima.common.exception.CustomException;
import com.heima.model.article.dto.ArticleDto;
import com.heima.model.wemedia.dtos.WmNewsDto;
import com.heima.model.wemedia.pojos.WmChannel;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.wemedia.bean.ContentBean;
import com.heima.wemedia.bean.ImageCheckResult;
import com.heima.wemedia.bean.TextCheckResult;
import com.heima.wemedia.mapper.WmNewsMapper;
import com.heima.wemedia.service.*;
import com.sun.xml.bind.v2.TODO;
import org.apache.kafka.common.protocol.types.Field;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Transactional   // 用于数据回滚保持数据的一致性
public class WmNewsAutoScanServiceImpl implements WmNewsAutoScanService {


    @Resource
    private WmNewsMapper wmNewsMapper;

    @Resource
    private IArticleClient articleClient;

    @Resource
    private GreenService greenService;

    @Resource
    private WmChannelService wmChannelService;

    @Resource
    private WmUserService wmUserService;

    private static final int CONTENT_TYPE_TEXT = 1;

    private static final int CONTENT_TYPE_IMAGE = 2;

    @Override
    @Async   // 调用的时候，进行异步。
    public void autoScanWmNews(Integer id) {
//        1,根据id 查询文章。 因为要审核内容.
        WmNews wmNews = this.wmNewsMapper.selectById(id);
//        查找之后进行判断，是否找到该文章。
        if (ObjectUtil.isEmpty(wmNews)) {
            throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST);
        }
//      进行状态判断。如果不是待审核就直接return
        if (ObjectUtil.notEqual(wmNews.getStatus(), WmNews.Status.SUBMIT.getCode())) {
            return;
        }
//        对文本进行审核，主要是 title 加 content 里面的内容进行审核，所以我们 设定一个类对应。
//        先写拿 出 text 或者image 的方法。
        List<String> textList = this.extractTextOrImages(wmNews, CONTENT_TYPE_TEXT);
//        审核纯文本
        boolean isHandleText = this.handleTextScan(StrUtil.join(",", textList), wmNews);
        if (!isHandleText) {
//            出现敏感词就return。
            return;
        }

//        对图片进行审核，主要是对 content 的image 和封面进行审核。
        List<String> imageList = this.extractTextOrImages(wmNews, CONTENT_TYPE_IMAGE);
        boolean isHandleImage = this.handleImageScan(imageList, wmNews);
        if (!isHandleImage) {
            return;
        }


        //        审核完之后，进行发布+加状态修改，注意，如果调用feign失败，造成数据不一致，回滚。
        //4. 更新文章状态，审核通过，调用文章微服务发布文章

        ResponseResult<Long> responseResult = this.saveAppArticle(wmNews);
        if (ObjectUtil.notEqual(responseResult.getCode(), 200)) {
            //抛出异常的作用，回滚事务
            throw new CustomException(AppHttpCodeEnum.SERVER_ERROR);
        }

        wmNews.setArticleId(responseResult.getData()); //设置文章id
        this.updateWmNews(wmNews, WmNews.Status.PUBLISHED, "审核通过");

    }


    /**
     * 保存app端相关的文章数据,把一些文章信息封装进去。
     */
    private ResponseResult<Long> saveAppArticle(WmNews wmNews) {
        ArticleDto dto = BeanUtil.toBean(wmNews, ArticleDto.class);
        //文章的布局
        dto.setLayout(wmNews.getType());
        //频道
        WmChannel wmChannel = this.wmChannelService.getById(wmNews.getChannelId());
        if (wmChannel != null) {
            dto.setChannelName(wmChannel.getName());
        }

        //作者
        dto.setAuthorId(wmNews.getUserId().longValue());
        WmUser wmUser = this.wmUserService.getById(wmNews.getUserId());
        if (wmUser != null) {
            dto.setAuthorName(wmUser.getName());
        }

        //设置文章id
        dto.setId(wmNews.getArticleId());

        dto.setCreatedTime(new Date());
        return articleClient.saveArticle(dto);
    }

    /**
     * 修改文章内容
     */
    private void updateWmNews(WmNews wmNews, WmNews.Status status, String reason) {
        wmNews.setStatus(status.getCode());
        wmNews.setReason(reason);
        this.wmNewsMapper.updateById(wmNews);
    }

    /**
     * 审核图片内容
     */
    private boolean handleImageScan(List<String> images, WmNews wmNews) {
//        1，先判断 图片集合是否为null
        if (CollUtil.isEmpty(images)) {
            return true;
        }
//        然后进行审核。审核的话，存在一张图片为风险就return 就行。
        for (String image : images) {
            ImageCheckResult imageCheckResult = this.greenService.imageCheck(image);
            String riskLevel = imageCheckResult.getRiskLevel();
            if (StrUtil.equalsAny(riskLevel, "low", "medium") || StrUtil.isEmpty(riskLevel)) {
                String reason = StrUtil.format("当前文章中的图片存在不确定内容，风险等级为：{}，内容为：{}",
                        riskLevel,
                        StrUtil.join("|", imageCheckResult.getDescriptionList()));
                updateWmNews(wmNews, WmNews.Status.ADMIN_AUTH, reason);
                return false;
            } else if (StrUtil.equals(riskLevel, "high")) {
                String reason = StrUtil.format("当前文章的图片中存在违规内容，风险等级为：{}，内容为：{}",
                        riskLevel,
                        StrUtil.join("|", imageCheckResult.getDescriptionList()));
                updateWmNews(wmNews, WmNews.Status.FAIL, reason);
                return false;
            }
        }
        return true;
    }


    /**
     * 审核纯文本内容
     */
    private boolean handleTextScan(String content, WmNews wmNews) {
//        先拿出  风险水平 属性
        TextCheckResult textCheckResult = this.greenService.textCheck(content);
        String riskLevel = textCheckResult.getRiskLevel();

        //        无风险，
        if (StrUtil.equals(riskLevel, "none")) {
            return true;
        }
//        低风险，中风险，或者 空值对象 交给人工审核。
        if (StrUtil.equalsAny(riskLevel, "medium", "low") || StrUtil.isEmpty(riskLevel)) {
            String reason = StrUtil.format("当前文章存在不确定内容，风险等级为,{},风险词为:{}",
                    riskLevel, StrUtil.join("|", textCheckResult.getRiskWordsList()));
            this.updateWmNews(wmNews, WmNews.Status.FAIL, reason);
            return false;
        }
//        高风险。直接审核失败。
        String reason = StrUtil.format("当前文章存在不确定内容，风险等级为,{},风险词为:{}",
                riskLevel, StrUtil.join("|", textCheckResult.getRiskWordsList()));
        this.updateWmNews(wmNews, WmNews.Status.FAIL, reason);
        return false;


    }


    /**
     * 获取 文本 和图片 审核数据
     *
     * @param wmNews 文章
     * @param type   类型
     * @return
     */

    private List<String> extractTextOrImages(WmNews wmNews, int type) {
//        所以要拿到content 内容，json转成一个 自定义对象接收
        String content = wmNews.getContent();
//        对json 进行操作，转成一个集合。
        List<ContentBean> contentBeanList = JSONUtil.toList(content, ContentBean.class);
//        对该集合进行 流的遍历，过滤赛选，取值。
        List<String> collect = StreamUtil.of(contentBeanList)
                .filter(contentBean -> {
//                    过滤出来对象，条件是 下面，要返回。多个内容。
                    if (type == CONTENT_TYPE_TEXT) {
                        return StrUtil.equals(contentBean.getType(), "text");
                    }
                    return StrUtil.equals(contentBean.getType(), "image");
                })
//              通过map 拿到我们想要的对象的属性，然后下面收集一下。
                .map(ContentBean::getValue)
                .collect(Collectors.toList());
//        进一步判断 ，拿到 标题 或者封面。
        if (type == CONTENT_TYPE_TEXT) {
            collect.add(wmNews.getTitle());
        } else if (type == CONTENT_TYPE_IMAGE) {
//            添加封面，对字符串 逗号分割，成一个结合，添加 all。
            collect.addAll(StrUtil.split(wmNews.getImages(), ","));
        }

        return collect;

    }


}
